#include "Area.h"
#include "Graphics.h"

Area::Area(Config* config, ModelFactory* modelFactory, string Name, Graphics* graphics) {
	this->config       = config;
    this->modelFactory = modelFactory;
	this->Name         = Name;
    this->graphics     = graphics;
	this->ID           = config->GetNewID();
    InitializeArea();
}

//Implementation of this function is optional
void Area::ExportToFile(string fileName) {
	ofstream outFile;
	outFile.open(fileName.c_str());
	assert(!outFile.is_open());
}

void Area::InitializeArea() {
	ifstream* inFile = new ifstream;
	string filePath = (config->PathToAreas + Name);
    inFile->open((config->PathToAreas + Name).c_str());
	assert(inFile->is_open());
    while(!inFile->eof()) {
        Models.push_back(ReadModel(inFile));
    }
}

Model* Area::ReadModel(ifstream* inFile) {
	string temp, name;
	for(int i=0; i<3; i++)
		*inFile >> name;

	Model* model = new Model(config, name);
	float x, y, z;
	*inFile >> x >> y >> z;
	model->Translation = XMVectorSet(x, y, z, 0.0f);
	*inFile >> x >> y >> z;
	model->Scaling = XMVectorSet(x, y, z, 0.0f);
	*inFile >> x >> y >> z;
	model->Rotation = XMVectorSet(x, y, z, 0.0f);

	*inFile >> temp; //read "}"
	while(temp.compare("}") != 0) {
		if(temp.compare("Primitive") == 0) {
			model->AddPrimitive(ReadPrimitive(inFile));
		} else if(temp.compare("Model") == 0) {
			model->AddModel(ReadModel(inFile)); //recursive
		}
		*inFile >> temp; //read "}"
	}
	return model;
}

Primitive* Area::ReadPrimitive(ifstream* inFile) {
	Primitive* prim = new Primitive(config, "", "", graphics);
    prim = CreatePrimitive(*(&prim), inFile);
    prim = TexturePrimitive(*(&prim), inFile);

    string temp;
    *inFile >> temp; //read "}"
	return prim;
}

Primitive* Area::CreatePrimitive(Primitive* prim, ifstream* inFile) {
    string type;
	for(int i=0; i<2; i++)
		*inFile >> type;
    
    if(type.compare("cube") == 0) {
		prim = ReadCube(inFile);
	} else if(type.compare("plane") == 0) {
        prim = ReadPlane(inFile);
	} else if(type.compare("cylinder") == 0) {
		prim = ReadCylinder(inFile);
	} else if(type.compare("disk") == 0) {
		prim = ReadDisk(inFile);
	} else if(type.compare("sphere") == 0) {
		prim = ReadSphere(inFile);
	} else if(type.compare("torus") == 0) {
		prim = ReadTorus(inFile);
	} else {
		prim = NULL;
        assert(false); //trying to create a non-existing type of primitive
	}

	float x, y, z;
	*inFile >> x >> y >> z;
	prim->Scaling = XMVectorSet(x, y, z, 0.0f);
	*inFile >> x >> y >> z;
	prim->Rotation = XMVectorSet(x, y, z, 0.0f);
	*inFile >> prim->NrOfTextures;
    return prim;
}

Primitive* Area::TexturePrimitive(Primitive* prim, ifstream* inFile) {
    if(prim->NrOfTextures == 0) {
		string material;
        *inFile >> material;
		config->Materials.push_back(material);
        for(UINT i=0; i<prim->textureFaces->size(); i++) {
            prim->textureFaces->at(i)->material = material;
        }
    } else {
        int previousTextureIndex = 0;
        string previousTexture = "";
        for(int i=0; i<prim->NrOfTextures; i++) {
            string texture;
            *inFile >> texture;
			config->AddTexture(texture);
			int currentTextureIndex;
            *inFile >> currentTextureIndex;

            //place textures on all but the last group of textureFaces
            for(int k=previousTextureIndex/6; k<currentTextureIndex/6; k++) {
                prim->textureFaces->at(k)->texture = previousTexture;
            }
            previousTextureIndex = currentTextureIndex;
            previousTexture = texture;
        }
        //place textures on the last textureFaces
        for(int k=previousTextureIndex/6; k<(int)(prim->textureFaces->size()); k++) {
            prim->textureFaces->at(k)->texture = previousTexture;
        }
    }
    return prim;
}

Primitive* Area::ReadCube(ifstream* inFile) {
	string name;
	*inFile >> name;
	float faceLength;
    *inFile >> faceLength;
    float centerX, centerY, centerZ;
    *inFile >> centerX >> centerY >> centerZ;
	Primitive* result = modelFactory->Cube(faceLength, centerX, centerY, centerZ);
    result->Name = name;
	return result;
}

Primitive* Area::ReadPlane(ifstream* inFile) {
	string name;
	*inFile >> name;
	float faceLength;
    *inFile >> faceLength;
    float centerX, centerY, centerZ;
    *inFile >> centerX >> centerY >> centerZ;
    Primitive* result = modelFactory->Plane(faceLength, centerX, centerY, centerZ);
	result->Name = name;
	return result;
}

Primitive* Area::ReadCylinder(ifstream* inFile) {
	//Fill in later. Code for dynamically creating cylinders and spheres can be 
	//found in the Luna book.
	return NULL;
}

Primitive* Area::ReadDisk(ifstream* inFile) {
	//Fill in later. Code for dynamically creating cylinders and spheres can be 
	//found in the Luna book.
	return NULL;
}

Primitive* Area::ReadSphere(ifstream* inFile) {
	string name;
	*inFile >> name;
	float radius;
	*inFile >> radius;
	int nrOfSlices, nrOfRings;
	*inFile >> nrOfSlices >> nrOfRings;

	if (nrOfSlices < 3 || nrOfRings < 3) {
		throw std::out_of_range("You're using too few rings/slices to draw the sphere.");
	}

	float centerX, centerY, centerZ;
	*inFile >> centerX >> centerY >> centerZ;
	Primitive* result = modelFactory->Sphere(radius, centerX, centerY, centerZ, nrOfSlices, nrOfRings);
	result->Name = name;
	return result;
}

Primitive* Area::ReadTorus(ifstream* inFile) {
	//Fill in later. Code for dynamically creating cylinders and spheres can be 
	//found in the Luna book.
	return NULL;
}

Model* Area::GetModelByID(int id) {
	for(UINT i=0; i<Models.size(); i++) {
		if(Models.at(i)->ContainsModel(id)) {
			return Models.at(i)->GetModel(id);
		}
	}
	assert(false); //no model with this ID found!
	return NULL;
}

boolean Area::AddModel(Model* model) {
	if(!ExistsModel(model->ID)) {
		Models.push_back(model);
		return true;
	}
	return false;
}

boolean Area::RemoveModel(int id) {
	for(UINT i=0; i<Models.size(); i++) {
		if(Models.at(i)->ID == id) {
			Models.erase(Models.begin() + i);
			return true;
		} else if(Models.at(i)->RemoveModel(id)) {
			return true;
		}
	}
	return false;
}

boolean Area::ExistsModel(int id) {
	for(UINT i=0; i<Models.size(); i++) {
		if(Models.at(i)->ContainsModel(id)) {
			return true;
		}
	}
	return false;
}

int Area::GetNrOfPrimitives() {
	int result = 0;
	for(UINT i=0; i<Models.size(); i++) {
		result += Models.at(i)->GetNrOfPrimitives();
	}
	return result;
}

Primitive* Area::GetPrimitiveByID(int id) {
	for(UINT i=0; i<Models.size(); i++) {
		if(Models.at(i)->ContainsPrimitive(id)) {
			return Models.at(i)->GetPrimitive(id);
		}
	}
	assert(false); //primitive with this id does not exist in this area!
	return NULL;
}

//Fill this in
Primitive* Area::GetPrimitive(int primNumber) {
    return NULL;
}

boolean Area::RemovePrimitive(int id) {
	for(UINT i=0; i<Models.size(); i++) {
		if(Models.at(i)->RemovePrimitive(id)) {
			return true;
		}
	}
	return false;
}

Light* Area::GetLight(int id) {
	for(UINT i=0; i<Lights.size(); i++) {
		if(Lights.at(i)->ID == id) {
			return Lights.at(i);
		}
	}
	assert(false); //no light with this ID exists in this area
	return new Light(config);
}

boolean Area::RemoveLight(int id) {
	for(UINT i=0; i<Lights.size(); i++) {
		if(Lights.at(i)->ID == id) {
			Lights.erase(Lights.begin() + i);
			return true;
		}
	}
	return false;
}

int Area::GetNrOfIndices() {
	int result = 0;
	for(UINT i=0; i<Models.size(); i++) {
		result += Models.at(i)->GetNrOfIndices();
	}
	return result;
}

int Area::GetNrOfVertices() {
	int result = 0;
	for(UINT i=0; i<Models.size(); i++) {
		result += Models.at(i)->GetNrOfVertices();
	}
	return result;
}
